<?php

use app\admin\model\Attachment as AttachmentModel;

class Mailer
{
	private $connect = '';
	private $username = '';
	private $password = '';
	public $mail_obj = '';
	private $num = '';
	private $structure = null;

	function __construct($host, $username = 'shangyinwl@163.com', $password = 'TFNSWHMMOVMITCHD', $type='')
	{
		if ($type != 'imap' && $type != 'pop3') {
			echo 'Error: Undefined protocol type <br>';
			exit;
		}
		if ($type == 'imap') {
			$this->connect = '{' . (strpos($host, ':') !== false ? $host : $host . ':143') . '}INBOX';
		} else {
			$this->connect = '{' . (strpos($host, ':') !== false ? $host : $host . ':110/pop3') . '}INBOX';
		}
		try {
			$obj = imap_open($this->connect, $this->username = $username, $this->password = $password);
			if (!$obj) {
				echo 'Error: Login failed ' . imap_last_error();
			} else {
				$this->mail_obj = $obj;
			}
		} catch (\Exception $e) {
			echo 'Catch: Login failed :'.$username;
			echo json_encode(mb_convert_encoding(imap_errors(),'utf-8','GBK'));
			exit;
		}
	}
	/**
	 * 获取邮件总数
	 * @return int 条数
	 */
	public function GmailNum()
	{
		$this->num = imap_num_msg($this->mail_obj);
		return $this->num;
	}
	/**
	 * 获取邮件正文
	 * @param int 邮件id HTML格式
	 * @return string body
	 */
	public function GMailBody($mid)
	{
		$this->GBumBool($mid);
		//处理邮件附件 
		$files = $this->GetAttach($mid); // 获取邮件附件，返回的邮件附件信息数组 
		return $this->getBody($mid, $files);
	}
	/**
	 * 获取邮件头信息
	 * @param int 邮件id
	 * @return array from发送者 formName发送者名称 Subject标题 time 
	 */
	public function GMailTit($mid)
	{

		$this->GBumBool($mid);
		$str = imap_headerinfo($this->mail_obj, $mid); //获取邮件头信息
		$arr = [
			'date' => date('Y-m-d H:i:s', strtotime($str->date ?? '1970-01-01 08:00:00') ?: $str->udate), //The message date as found in its headers    收件时间
			'fromAddress' => '', //发件人地址
			'from' => [], //发件人信息
			'subject' => $this->decode_mime($str->subject ?? ''), //标题
			'mail_id' =>  $str->Msgno ?? '',
			'message_id' => trim(trim($str->message_id ?? '', '<'), '>'),
			'toAddress' => '', //收件人地址
			'to' => [], //收件人信息
			'replytoAddress' => '', //full Reply-To
			'replyto' => [],
			'ccAddress' => '', //抄送人地址
			'cc' => [], //抄送人信息
			'size' => $str->Size, //大小
			'udate' => $str->udate, //时间戳
			'isdel' => 0, //可查询即存在
		];
		($str->Deleted ?? '') == 'D' ? $arr['isdel'] = 2 : ''; //已删除(系统标记)
		$arr['message_id'] ?: $arr['message_id'] = $arr['subject'] . $arr['size'];
		foreach ($str->from ?? [] as $key => $from) {
			isset($from->personal) ? $from->personal = $this->decode_mime($from->personal) : '';
			$arr['from'][] = $from;
			$arr['fromAddress'] .= $from->mailbox . '@' . $from->host . ';'; //发件人地址
		}
		$arr['from'] = json_encode($arr['from']);
		if (strpos($str->toaddress ?? '', 'undisclosed-recipients') !== false || strpos($str->toaddress ?? '', 'Undisclosed recipients') !== false) { //匿名接收
			$arr['toAddress'] = 'undisclosed-recipients@;';
		} else {
			foreach ($str->to ?? [] as $key => $to) {
				isset($to->personal) ? $to->personal = $this->decode_mime($to->personal) : '';
				$arr['to'][] = $to;
				$arr['toAddress'] .= $to->mailbox . '@' . strtolower($to->host) . ';'; //收件人地址
			}
		}
		$arr['to'] = strtolower(json_encode($arr['to']));
		foreach ($str->reply_to ?? [] as $key => $reply_to) {
			isset($reply_to->personal) ? $reply_to->personal = $this->decode_mime($reply_to->personal) : '';
			$arr['replyto'][] = $reply_to;
			$arr['replytoAddress'] .= $reply_to->mailbox . '@' . $reply_to->host . ';'; //收件人地址
		}
		$arr['replyto'] = json_encode($arr['replyto']);
		foreach ($str->cc ?? [] as $key => $cc) {
			isset($cc->personal) ? $cc->personal = $this->decode_mime($cc->personal) : '';
			$arr['cc'][] = $cc;
			$arr['ccAddress'] .= $cc->mailbox . '@' . $cc->host . ';'; //收件人地址
		}
		$arr['cc'] = json_encode($arr['cc']);
		return $arr;
	}
	/**
	 * 删除邮件
	 * @param int 邮件Id
	 * @return bool 
	 */
	public function DelMail($id)
	{
		imap_delete($this->mail_obj, $id, 0);
		return imap_expunge($this->mail_obj);
	}
	/**
	 * 获取邮件body编码
	 * @param int 邮件id
	 * @return string 邮件编码
	 */
	public function Gcharset($id)
	{
		$str = imap_fetchstructure($this->mail_obj, $id);
		$errs = imap_errors(); //查询并清空错误列表
		if (isset($str->parts)) {
			return $str->parts[0]->parameters[0]->value;
		} else {
			return $str->parameters[0]->value;
		}
	}
	/**
	 * 判断数据越界
	 * @param int 邮件id
	 * @return bool 
	 */
	private function GBumBool($num)
	{
		// 检查连接状态
		if (imap_ping($this->mail_obj)) {
			// echo "Connection is active and working.\n";
		} else {
			// echo "Connection is not active.\n";

			$obj = imap_open($this->connect, $this->username, $this->password);
			if (!$obj) {
				echo 'Error: Login failed ' . imap_last_error();
				exit;
			} else {
				$this->mail_obj = $obj;
			}
		}

		if ($this->num == '') {
			$nu = $this->GmailNum();
			if ($num > $nu) {
				echo '邮件ID越界';
				exit;
			}
		} elseif ($num > $this->num) {
			echo '邮件ID越界';
			exit;
		}
	}

	/**
	 * decode_mime()转换邮件标题的字符编码,处理乱码 
	 */
	function decode_mime($str)
	{
		$str = imap_utf8($str);
		if (trim($str) == '') {
			return '';
		}
		$str = preg_replace("/\\x00/", "", $str); //清理截断
		$str = str_replace("GB2312''", '', $str);

		$pattern = '/^(?:%[0-9A-Fa-f]{2})+$/';
		if (preg_match($pattern, $str)) { //识别为十六进制数字序列
			$str = urldecode($str);
		}

		$str = imap_mime_header_decode($str);
		$newstr = '';
		if ($str) {
			foreach ($str as $key => $value) {
				$newstr .= trim($value->text);
			}
		} else {
			$newstr = $str;
		}
		$encode = mb_detect_encoding($newstr, array("ASCII", 'UTF-8', "GB2312", "GB18030", "GBK", "BIG5"));
		if ($encode == 'UTF-8') {
			return $newstr = mb_convert_encoding($newstr, 'UTF-8', 'UTF-8');
		} elseif ($encode == 'GB18030') {
			return iconv("gb18030", "utf-8", $newstr);
		} elseif ($encode == 'GB2312' || $encode == 'EUC-CN') {
			return iconv("GBK", "utf-8", $newstr);
		}
		return $newstr;
	}

	// 根据不同编码进行解码
	function convertByencoding($str, $encoding)
	{
		if (trim($str) == '') {
			return '';
		}
		$encode = mb_detect_encoding($str, array("ASCII", 'UTF-8', "GB2312", "GB18030", "GBK", "BIG5"));
		if ($encode == 'UTF-8') {
			return mb_convert_encoding($str, 'UTF-8', 'UTF-8');
		} elseif ($encode == 'GB18030') {
			return iconv("gb18030", "utf-8", $str);
		}
		if ($encoding == 0) { // 7 位 ASCII 编码。常用于纯文本内容，没有特殊字符和控制字符。
			$str = imap_8bit($str);
		} elseif ($encoding == 1) { //8 位 ASCII 编码。常用于纯文本内容，可以包含 8 位字符。
			$str = imap_8bit($str);
		} elseif ($encoding == 2) { //二进制编码。适用于包含非文本数据的部分，例如图片、音频等。
			$str = imap_binary($str);
		} elseif ($encoding == 3) { //图片  二进制编码。适用于包含非文本数据的部分，例如图片、音频等。
			$str = $this->decode_mime(imap_base64($str));
		} elseif ($encoding == 4) { //Quoted-Printable 编码。常用于将二进制数据编码成可安全传输的文本格式，适用于附件等。
			$str = imap_qprint($str);
			$encode = mb_detect_encoding($str, array("ASCII", 'UTF-8', "GB2312", "GB18030", "GBK", "BIG5"));
			if ($encode == 'UTF-8') {
				$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
			} else {
				$str = iconv('GBK', 'utf8//IGNORE', $str);
			}
		} else {
			$str =  iconv('GBK', 'utf8//IGNORE', $str);
		}
		return $str;
	}

	/** 

	 * Set path name of the uploaded file to be saved. 

	 * 

	 * @param int  $fileID 

	 * @param string $extension 

	 * @access public 

	 * @return string 

	 */

	public function setPathName($fileID, $extension)
	{
		return date('Ym/dHis', time()) . $fileID . mt_rand(0, 10000) . '.' . $extension;
	}
	function GetAttach($mid) // Get Atteced File from Mail 
	{
		if (!$this->mail_obj)
			return false;
		$structure = imap_fetchstructure($this->mail_obj, $mid);
		$errs = imap_errors(); //查询并清空错误列表

		$files = array();
		if ($structure->parts ?? '') {
			foreach ($structure->parts as $key => $value) {
				$file = [];
				$enc = $value->encoding;
				if ($value->ifdparameters) { //表示这部分的内容是可提取的
					if (((@$value->disposition == "ATTACHMENT") || ($value->ifid ?? '')) || $value->type == 3) {
						//取邮件附件 
						$file['type']   = 1; //附件
						$name = '';
						foreach ($value->dparameters as $dparameter) {
							if (strpos(strtolower($dparameter->attribute), 'filename') !== false) {
								$name .= $this->decode_mime($dparameter->value); //拼接文件名
							}
						}
						$extend = explode(".", $name);
						$file['extension'] = $extend[count($extend) - 1];
						$file['title']   = $name;
						$message = imap_fetchbody($this->mail_obj, $mid, $key + 1);

						if ($enc == 0) // 7 位 ASCII 编码。常用于纯文本内容，没有特殊字符和控制字符。
							$message = imap_8bit($message);
						if ($enc == 1) //8 位 ASCII 编码。常用于纯文本内容，可以包含 8 位字符。
							$message = imap_8bit($message);
						if ($enc == 2) //二进制编码。适用于包含非文本数据的部分，例如图片、音频等。
							$message = imap_binary($message);
						if ($enc == 3) { //图片  二进制编码。适用于包含非文本数据的部分，例如图片、音频等。
							$message = imap_base64($message);
						}
						if (
							$enc == 4
						) //Quoted-Printable 编码。常用于将二进制数据编码成可安全传输的文本格式，适用于附件等。
							$message = quoted_printable_decode($message);
						if ($enc == 5) //其他编码方式。这是一个保留字段，表示其他可能的编码方式。
							$message = $message;
						$file_exists = AttachmentModel::toAttachment('fileContents://' . $message, 'email', 'files/email/' . str_replace("/", "\\", $file['title']), '', 'attachment_id:url@email_detail_file;:body@email_detail');
						$file['url'] = AttachmentModel::getUrl($file_exists['driver'] ?? '', $file_exists['path'] ?? '');
						$file['attachment_id'] = $file_exists['id'];
						$file['cid'] = trim(trim($value->id ?? '', '<'), '>');
						$files[] = $file;
					}
				} else if ($value->parts ?? '') {
					// 处理内容中包含图片的部分 
					foreach ($value->parts as $keyb => $valueb) {
						$enc = $value->parts[$keyb]->encoding;
						if ($value->parts[$keyb]->ifdparameters) {
							//命名图片 
							$name = $this->decode_mime($value->parts[$keyb]->dparameters[0]->value);
							$extend = explode(".", $name);
							$file['extension'] = $extend[count($extend) - 1];
							$file['title']   = $name;
							$file['type']   = 0;

							$partnro = ($key + 1) . "." . ($keyb + 1);
							$message = imap_fetchbody($this->mail_obj, $mid, $partnro);
							if ($enc == 0)
								$message = imap_8bit($message);
							if ($enc == 1)
								$message = imap_8bit($message);
							if ($enc == 2)
								$message = imap_binary($message);
							if ($enc == 3) {
								$message = imap_base64($message);
							}
							if ($enc == 4)
								$message = quoted_printable_decode($message);
							if ($enc == 5)
								$message = $message;

							$file_exists = AttachmentModel::toAttachment('fileContents://' . $message, 'email', 'images/email/' . $file['title'], '', 'attachment_id:url@email_detail_file;:body@email_detail');
							$file['url'] = (AttachmentModel::getUrl($file_exists['driver'] ?? '', $file_exists['path'] ?? ''));
							$file['attachment_id'] = $file_exists['id'];
							$file['cid'] = strtolower(trim(trim($value->id ?? '', '<'), '>'));
							$files[] = $file;
						}
					}
				}else if (($value->ifid ?? '')&&($value->type == 3)&&(@$value->subtype == "OCTET-STREAM")) {//指示邮件部分是否有一个特定的ID，用于识别和引用邮件正文内嵌的元素
					//取邮件附件 
					$file['type']   = 0;
					$file['title']   = '';
					$message = imap_fetchbody($this->mail_obj, $mid, $key + 1);

					if ($enc == 0) // 7 位 ASCII 编码。常用于纯文本内容，没有特殊字符和控制字符。
						$message = imap_8bit($message);
					if ($enc == 1) //8 位 ASCII 编码。常用于纯文本内容，可以包含 8 位字符。
						$message = imap_8bit($message);
					if ($enc == 2) //二进制编码。适用于包含非文本数据的部分，例如图片、音频等。
						$message = imap_binary($message);
					if ($enc == 3) { //图片  二进制编码。适用于包含非文本数据的部分，例如图片、音频等。
						$message = imap_base64($message);
					}
					if ($enc == 4) //Quoted-Printable 编码。常用于将二进制数据编码成可安全传输的文本格式，适用于附件等。
						$message = quoted_printable_decode($message);
					if ($enc == 5) //其他编码方式。这是一个保留字段，表示其他可能的编码方式。
						$message = $message;
					$file_exists = AttachmentModel::toAttachment('fileContents://' . $message, 'email', 'files/email/' , '', 'attachment_id:url@email_detail_file;:body@email_detail');
					$file['cid'] = strtolower(trim(trim($value->id ?? '', '<'), '>'));
					$file['title']   = $file['cid'].'.'. $file_exists['ext'];
					$file_exists = AttachmentModel::getAttachmentExists(['id'=>$file_exists['id']], $file['title']);//重命名
					$file['extension'] = $file_exists['ext'];
					$file['url'] = AttachmentModel::getUrl($file_exists['driver'] ?? '', $file_exists['path'] ?? '');
					$file['attachment_id'] = $file_exists['id'];
					$files[] = $file;
				}
			}
		}
		return $files;
	}

	//移动邮件到指定分组 
	function move_mails($msglist, $mailbox)
	{
		if (!$this->mail_obj)
			return false;
		imap_mail_move($this->mail_obj, $msglist, $mailbox);
	}


	function getBody($mid, $files) // Get Message Body 
	{
		if (!$this->mail_obj)
			return false;

		$body = '';
		$this->structure = imap_fetchstructure($this->mail_obj, $mid); //存在Warning: MIME header encountered in non-MIME message (errflg=3)
		$errs = imap_errors(); //查询并清空错误列表

		$mime_type = $this->get_mime_type($this->structure);
		if ($mime_type == 'MULTIPART/MIXED') {
			// 遍历邮件的各个部分
			foreach ($this->structure->parts as $partNumber => $part) {
				if ($part->ifdparameters) {
					//忽略已处理邮件附件 
					continue;
				}
				// 获取文本内容部分
				if ($part->type == 0) { //0 (TYPETEXT): 表示文本内容部分。这可以是邮件的主体文本部分，也可以是邮件中的一些文本内容。
					$mailContent = imap_fetchbody($this->mail_obj, $mid, $partNumber + 1);
					$encoding = $part->encoding;
					$body .= $this->convertByencoding($mailContent, $encoding);
				} else if ($part->type == 1) { //1 (TYPEMULTIPART): 表示多部分邮件。这种类型的邮件包含多个不同类型的部分，通常是混合内容类型，比如同时包含了文本和附件。
					//'MULTIPART/ALTERNATIVE'	处理
					$body = $this->get_part($mid, "TEXT/HTML");
					if ($body == ""){
						$body = $this->get_part($mid, "TEXT/PLAIN");
					}
				} else if ($part->type == 3) { //3 (TYPEAPPLICATION): 表示邮件中的应用程序数据部分。这可以是二进制数据，例如附件。
					if ($part->ifid&&(@$part->subtype == "OCTET-STREAM")){//指示邮件部分是否有一个特定的ID，用于识别和引用邮件正文内嵌的元素
						continue;
					}else{
						// 获取附件的名称
						$attachmentName = $part->parameters[0]->value;
						// 保存附件到本地
						$attachmentContent = imap_fetchbody($this->mail_obj, $mid, $partNumber + 1);
						file_put_contents($attachmentName, $attachmentContent);
						echo "Attachment saved: $attachmentName\n";
						die;
					}
				}
			}
		} else {
			$body = $this->get_part($mid, "TEXT/HTML");
			if ($body == "")
				$body = $this->get_part($mid, "TEXT/PLAIN");
			if ($body == "") {
				return "";
			}
		}
		//处理图片 
		$body = $this->embed_images($body, $files);
		return $body;
	}


	function embed_images($body, $files)
	{
		// get all img tags 
		preg_match_all('/<img.*?>/s',strtolower($body), $matches);
		if (!isset($matches[0])) return;
		$files = array_column($files, 'url', 'cid');
		foreach ($matches[0] as $img) {
			// replace image web path with local path 
			preg_match('/src="(.*?)"/', $img, $m);
			if (!isset($m[1])) continue;
			if (strpos($m[1], 'cid:') !== false) {
				$m[1] = str_replace('cid:', '', $m[1]);
				if ($imgurl = $files[$m[1]] ?? '') {
					$body = str_ireplace('cid:' . $m[1], $imgurl, $body);//大小写不敏感
				}
			} else {
				$file_exists = AttachmentModel::toAttachment($m[1], 'email', 'images/email/*.*', '', ':body@email_detail'); //图片本地化
				if ($file_exists) {
					$body = str_ireplace($m[1], (AttachmentModel::getUrl($file_exists['driver'] ?? '', $file_exists['path'] ?? '')), $body);//大小写不敏感
				}
			}
		}
		return $body;
	}


	function get_part($mid, $mime_type,$structure = false, $part_number = false) //Get Part Of Message Internal Private Use 
	{
		if (!$structure) {
			$structure = $this->structure;
		}
		if ($mime_type == $this->get_mime_type($structure)) {
			if (!$part_number) {
				$part_number = "1";
			}
			$text = imap_fetchbody($this->mail_obj, $mid, $part_number);
			return $this->convertByencoding($text,  $structure->encoding);
		}
		if ($structure->type == 1) /* multipart */ {
			foreach ($structure->parts as $index => $sub_structure) {
				if ($part_number) {
					$prefix = $part_number . '.';
				} else {
					$prefix = '';
				}
				$data = $this->get_part(
					$mid,
					$mime_type,
					$sub_structure,
					$prefix . ($index + 1)
				);
				if ($data) {
					return $data;
				}
			}
		}
		return false;
	}


	function get_mime_type(&$structure) //Get Mime type Internal Private Use 
	{

		$primary_mime_type = array("TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO", "OTHER");

		if ($structure->subtype && $structure->subtype != "PNG") {

			return $primary_mime_type[(int) $structure->type] . '/' . $structure->subtype;
		}

		return "TEXT/PLAIN";
	}
}
